// -------------------------------------------------
// BlinKit - BlinKit Library
// -------------------------------------------------
//   File Name: mouse_session.cpp
// Description: MouseSession Class
//      Author: Ziming Li
//     Created: 2021-11-23
// -------------------------------------------------
// Copyright (C) 2021 MingYang Software Technology.
// -------------------------------------------------

#include "./mouse_session.h"

#include <windowsx.h>
#include "blinkit/ui/input_events.h"

namespace BlinKit {

WebInputEvent::Type MouseSession::AdjustEventTypeForMouseMove(void)
{
    if (m_mouseEntered)
        return WebInputEvent::MouseMove;
    m_mouseEntered = true;
    return WebInputEvent::MouseEnter;
}

int MouseSession::AdjustModifiers(UINT flags)
{
    int ret = 0;
    if (MK_CONTROL & flags)
        ret |= WebInputEvent::ControlKey;
    if (MK_SHIFT & flags)
        ret |= WebInputEvent::ShiftKey;
    if (GetAsyncKeyState(VK_MENU) & 0x8000)
        ret |= WebInputEvent::AltKey;
    return ret;
}

void MouseSession::Process(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam, const Callback &callback)
{
    bool trackLeave = m_leaveTrackingEnabled
        ? !m_mouseEntered
        : false;

    switch (msg)
    {
        case WM_LBUTTONDOWN:
            SetCaptureIfPossible(hWnd);
            break;
        case WM_LBUTTONUP:
            ReleaseCapture();
            break;
    }

    MouseEvent e = Prepare(msg, wParam, lParam);

    callback(e.Translate());

    m_lastPosition = e.Position();
    switch (e.GetType())
    {
        case WebInputEvent::MouseDown:
            m_lastMouseDownTime = e.TimeStampInSeconds();
            break;
        case WebInputEvent::MouseLeave:
            m_mouseEntered = false;
            break;
    }

    if (trackLeave)
    {
        TRACKMOUSEEVENT tme = { 0 };
        tme.cbSize = sizeof(tme);
        tme.dwFlags = TME_LEAVE;
        tme.hwndTrack = hWnd;
        TrackMouseEvent(&tme);
    }
}

MouseEvent MouseSession::Prepare(UINT msg, WPARAM wParam, LPARAM lParam)
{
    WebInputEvent::Type type = WebInputEvent::Undefined;
    WebPointerProperties::Button button = WebMouseEvent::ButtonNone;

    UINT flags = wParam;
    switch (msg)
    {
        case WM_LBUTTONDOWN:
            type = WebInputEvent::MouseDown;
            button = WebPointerProperties::ButtonLeft;
            break;
        case WM_LBUTTONUP:
            type = WebInputEvent::MouseUp;
            button = WebPointerProperties::ButtonLeft;
            break;
        case WM_MOUSEMOVE:
            type = AdjustEventTypeForMouseMove();
            if (MK_LBUTTON & flags)
                button = WebPointerProperties::ButtonLeft;
            else if (MK_RBUTTON & flags)
                button = WebPointerProperties::ButtonRight;
            else if (MK_MBUTTON & flags)
                button = WebPointerProperties::ButtonMiddle;
            break;
        case WM_RBUTTONDOWN:
            type = WebInputEvent::MouseDown;
            button = WebPointerProperties::ButtonRight;
            break;
        case WM_RBUTTONUP:
            type = WebInputEvent::MouseUp;
            button = WebPointerProperties::ButtonRight;
            break;
        case WM_MOUSELEAVE:
            type = WebInputEvent::MouseLeave;
            break;
        case WM_MBUTTONDOWN:
            type = WebInputEvent::MouseDown;
            button = WebPointerProperties::ButtonMiddle;
            break;
        case WM_MBUTTONUP:
            type = WebInputEvent::MouseUp;
            button = WebPointerProperties::ButtonMiddle;
            break;
        default:
            NOTREACHED();
    }

    int x = GET_X_LPARAM(lParam);
    int y = GET_Y_LPARAM(lParam);

    MouseEvent ret(type, button, x, y);
    ret.SetModifiers(AdjustModifiers(flags));
    ret.AdjustMovement(m_lastPosition);
    if (WebInputEvent::MouseDown == type)
        ret.AdjustClickCount(m_lastMouseDownTime, m_lastMouseDownPosition);
    return ret;
}

void MouseSession::ReleaseCapture(void)
{
    if (0 == m_captureCount)
        return;

    if (0 == --m_captureCount)
        ::ReleaseCapture();
}

void MouseSession::SetCaptureIfPossible(HWND hWnd)
{
    ASSERT(nullptr != hWnd);
    if (0 == m_captureCount)
    {
        if (GetCapture() != nullptr)
            return;

        ::SetCapture(hWnd);
    }
    else
    {
        ASSERT(GetCapture() == hWnd);
    }
    ++m_captureCount;
}

} // namespace BlinKit
